﻿using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

using Serilog;

using System.Collections.Concurrent;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace GitHubCopilotProxies.Handlers;

public class CopilotCompletions
{
    internal class DebounceSemaphore
    {
        private readonly ConcurrentDictionary<string, CancellationTokenSource> _entries = new();
        public async Task<bool> DebounceAsync(string token, int minDelayMs, int maxDelayMs, CancellationToken cancellationToken = default)
        {
            using var newEntry = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
            var existingCts = _entries.AddOrUpdate(token, newEntry, (key, existing) =>
            {
                try { existing.Cancel(); existing.Dispose(); } // 取消并释放旧的 CTS
                catch (ObjectDisposedException) { /* 已被释放，忽略 */ }
                return newEntry; // 返回新的 CTS
            });
            // 如果 AddOrUpdate 返回的不是我们刚刚创建的 newEntry，说明在并发下有其他线程更新了，当前操作被“覆盖”，直接返回 false
            if (existingCts != newEntry)
            {
                newEntry.Dispose(); // 释放未使用的 newEntry
                return false; // 防抖生效
            }

            try
            {
                var delay = Random.Shared.Next(minDelayMs, maxDelayMs);
                await Task.Delay(delay, newEntry.Token);
                // 延迟结束后，尝试移除条目。只有当字典中的 CTS 仍然是 newEntry 时才移除成功。
                if (_entries.TryRemove(KeyValuePair.Create(token, newEntry)))
                {
                    newEntry.Dispose(); // 成功移除后释放
                    return true; // 延迟完成且未被覆盖
                }
                return false; // 在延迟期间被新的请求覆盖了
            }
            catch (OperationCanceledException)
            {
                // 尝试移除，确保清理
                _entries.TryRemove(KeyValuePair.Create(token, newEntry));
                newEntry.Dispose(); // 释放
                return false; // 操作被取消
            }
        }
    }

    static readonly DebounceSemaphore CodeDebounceSemaphore = new();
    static async Task ForwardStreamAsync(HttpClient httpClient, HttpRequestMessage httpRequest, HttpContext httpContext, int bufferSize = 4096 / 4 / 4)
    {
        using var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, httpContext.RequestAborted);
        httpContext.Response.StatusCode = (int)response.StatusCode;
        try { response.EnsureSuccessStatusCode(); }
        catch { try { Log.Warning(await response.Content.ReadAsStringAsync()); } finally { } throw; }
        if (response.Content.Headers.ContentType?.MediaType != null)
            httpContext.Response.ContentType = response.Content.Headers.ContentType!.MediaType;
        using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
        await responseStream.CopyToAsync(httpContext.Response.Body, bufferSize, httpContext.RequestAborted);
        await httpContext.Response.CompleteAsync();
    }
    static async Task ForwardStreamMsg2TxtAsync(HttpClient httpClient, HttpRequestMessage httpRequest, HttpContext httpContext, int bufferSize = 4096 / 4 / 4)
    {
        using var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, httpContext.RequestAborted);
        httpContext.Response.StatusCode = (int)response.StatusCode;
        try { response.EnsureSuccessStatusCode(); }
        catch { try { Log.Warning(await response.Content.ReadAsStringAsync()); } finally { } throw; }

        var mediaType = response.Content.Headers.ContentType?.MediaType;
        if (mediaType != null) httpContext.Response.ContentType = mediaType;

        using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
        if (mediaType?.Contains("event-stream") is true)
        {
            using var reader = new StreamReader(responseStream);
            var firstLineProcessed = false; // 标记是否已处理完首行（无论是否是 ```）
            var firstLineBuffer = new StringBuilder(); // 用于缓存并检测首行内容
            while (!reader.EndOfStream)
            {
                if (httpContext.RequestAborted.IsCancellationRequested) break;
                var line = await reader.ReadLineAsync();
                if (string.IsNullOrEmpty(line)) continue;
                if (line.StartsWith("data: {"))
                {
                    var jsonObj = JsonObject.Parse(line["data: ".Length..]);
                    if (jsonObj == null) continue;
                    jsonObj["object"] = "text_completion";
                    foreach (JsonObject choice in jsonObj["choices"]!.AsArray()!)
                    {
                        var currentContent = choice["delta"]?["content"]?.GetValue<string>()
                            ?? choice["message"]?["content"]?.GetValue<string>() ?? string.Empty;
                        string outputContent = string.Empty; // 当前 choice 最终输出的内容
                        if (!firstLineProcessed)
                        {
                            firstLineBuffer.Append(currentContent);
                            var bufferContent = firstLineBuffer.ToString();
                            if (bufferContent.Length > 3 && !bufferContent.StartsWith("```"))
                            {
                                // 获得的字符数量已足够判断是否为代码块开头
                                // 如果首行不是代码块标记时直接原样输出内容
                                firstLineProcessed = true;
                                outputContent = bufferContent;
                                firstLineBuffer.Clear();
                            }
                            else
                            {
                                var newLinePos = bufferContent.IndexOf('\n');
                                if (newLinePos >= 0) // 找到首个换行符，可以处理首行了
                                {
                                    firstLineProcessed = true;
                                    var firstLine = bufferContent.AsSpan(0, newLinePos).TrimEnd('\r');
                                    var remainingContent = bufferContent[(newLinePos + 1)..]; // 换行符之后的内容
                                    if (firstLine.StartsWith("```")) outputContent = remainingContent; // 输出换行符之后的部分
                                    else outputContent = bufferContent; // 首行不是代码块，原样输出首行
                                    firstLineBuffer.Clear(); // 清空缓冲区
                                }
                                else outputContent = string.Empty;
                            }
                        }
                        else outputContent = currentContent;
                        choice["text"] = outputContent;
                        choice.Remove("delta");
                        choice.Remove("message");
                    }
                    await httpContext.Response.WriteAsync($"data: {jsonObj.ToJsonString()}\n\n");
                }
                else { await httpContext.Response.WriteAsync($"{line}\n\n"); }
            }
        }
        else await responseStream.CopyToAsync(httpContext.Response.Body, bufferSize, httpContext.RequestAborted);
        await httpContext.Response.CompleteAsync();
    }
    static async Task ForwardStreamReasoning2ContentAsync(HttpClient httpClient, HttpRequestMessage httpRequest, HttpContext httpContext, int bufferSize = 4096 / 4 / 4)
    {
        using var response = await httpClient.SendAsync(httpRequest, HttpCompletionOption.ResponseHeadersRead, httpContext.RequestAborted);
        httpContext.Response.StatusCode = (int)response.StatusCode;
        try { response.EnsureSuccessStatusCode(); }
        catch { try { Log.Warning(await response.Content.ReadAsStringAsync()); } finally { } throw; }

        var mediaType = response.Content.Headers.ContentType?.MediaType;
        if (mediaType != null) httpContext.Response.ContentType = mediaType;

        using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
        if (mediaType?.Contains("event-stream") is true)
        {
            using var reader = new StreamReader(responseStream);
            bool isFirstChunk = true;
            bool shouldConvert = false;
            bool isInThinkBlock = false;
            while (!reader.EndOfStream)
            {
                if (httpContext.RequestAborted.IsCancellationRequested) break;
                var line = await reader.ReadLineAsync();
                if (string.IsNullOrEmpty(line)) continue;
                if (line.StartsWith("data: {"))
                {
                    if (isFirstChunk == false && shouldConvert == false && isInThinkBlock == false)
                    {
                        await httpContext.Response.WriteAsync($"{line}\n\n");
                        continue;
                    }

                    var jsonObj = JsonObject.Parse(line["data: ".Length..]);
                    if (jsonObj == null) continue;
                    if (isFirstChunk)
                    {
                        isFirstChunk = false; // 根据第一个 token 的返回判断是否需要进行思维链块转换
                        shouldConvert = jsonObj["choices"]?.AsArray()?.Any(c => c?["delta"]?["reasoning_content"] != null) ?? false;
                    }
                    if (shouldConvert)
                        foreach (JsonObject choice in jsonObj["choices"]!.AsArray()!)
                        {
                            var delta = choice["delta"];
                            if (delta is null) continue;
                            var reasoningContent = (delta["reasoning_content"] ?? delta["reasoning"])?.GetValue<string>();
                            var normalContent = delta["content"]?.GetValue<string>();
                            if (!isInThinkBlock && reasoningContent != null)
                            {
                                choice["delta"]!["content"] = $"> {reasoningContent}";
                                isInThinkBlock = true;
                            }
                            else if (isInThinkBlock && normalContent != null && reasoningContent == null)
                            {
                                choice["delta"]!["content"] = $"\n\n{normalContent}";
                                isInThinkBlock = false;
                                shouldConvert = false; // 转换结束，后续原样输出
                            }
                            else if (isInThinkBlock && reasoningContent != null)
                            {
                                reasoningContent = reasoningContent.Replace("\n", "\n> ");
                                choice["delta"]!["content"] = reasoningContent;
                            }
                        }
                    await httpContext.Response.WriteAsync($"data: {jsonObj.ToJsonString()}\n\n");
                }
                else await httpContext.Response.WriteAsync($"{line}\n\n");
            }
        }
        else await responseStream.CopyToAsync(httpContext.Response.Body, bufferSize, httpContext.RequestAborted);
        await httpContext.Response.CompleteAsync();
    }

    private static readonly Regex SystemRegex = new Regex(@"<SYSTEM>(?<system>[\s\S]*?)</SYSTEM>(?<prompt>[\s\S]*)", RegexOptions.Singleline | RegexOptions.Compiled);
    public static async Task CodeCompletions(HttpContext httpContext,
        [FromServices] IHostEnvironment hostEnvironment,
        [FromServices] IHttpClientFactory httpClientFactory,
        [FromServices] IOptionsSnapshot<SystemConfig> systemConfig,
        [FromKeyedServices("CodeCompletions")] ConfigCache<SystemConfig.CodeCompletionsConfig> codeCacheOptions,
        [FromKeyedServices("EditCompletions")] ConfigCache<SystemConfig.CodeCompletionsConfig> editCacheOptions)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            await httpContext.Response.CompleteAsync();
            return;
        }

        var baseOptions = systemConfig.Value.CodeCompletions!;
        bool isEditCompletions = httpContext.Request.Path.Value?.EndsWith("/speculation") is true;
        if (isEditCompletions is false && baseOptions is { Debounce: not null and not { Min: 0, Max: 0 } }) // 非编辑补全请求，增加防抖时间，避免多次请求
        {
            var token = httpContext.Request.Headers.Authorization.ToString();
            if (await CodeDebounceSemaphore.DebounceAsync(token, baseOptions.Debounce.Min, baseOptions.Debounce.Max, httpContext.RequestAborted) == false)
            {
                httpContext.Response.Headers.CacheControl = "no-cache";
                httpContext.Response.StatusCode = StatusCodes.Status204NoContent;
                await httpContext.Response.Body.FlushAsync();
                httpContext.Abort();
                return;
            }
        }

        httpContext.Response.Headers["azureml-model-deployment"] = $"d{Random.Shared.Next(1, 900).ToString().PadLeft(3, '0')}-20250129112839";
        httpContext.Response.Headers["Content-Security-Policy"] = "default-src 'none'; sandbox";
        httpContext.Response.Headers["x-github-backend"] = "Kubernetes";
        httpContext.Response.Headers["x-request-id"] = httpContext.Request.Headers["x-request-id"];
        httpContext.Response.Headers["X-GitHub-Request-Id"] = "C0E1:6A1A:1A1F:2A1D:1A1F:1A1F:1A1F:1A1F";

        bool isRequestSended = false;
        string? apiUrl = null, apiKey = null, model = null;
        try
        {
            bool useProxy = false;
            var bodyJson = await httpContext.Request.ReadFromJsonAsync<JsonElement>(httpContext.RequestAborted);
            var updatedBody = JsonObject.Create(bodyJson)!;

            if (isEditCompletions is false)
            {
                var codeOptions = codeCacheOptions.Get();
                apiUrl = codeOptions.ApiUrl;
                apiKey = codeOptions.ApiKey;
                useProxy = codeOptions.UseProxy;
                updatedBody["model"] = model = codeOptions.Model;
            }
            if (isEditCompletions is true)
            {
                var editOptions = editCacheOptions.Get();
                apiUrl = editOptions.ApiUrl;
                apiKey = editOptions.ApiKey;
                useProxy = editOptions.UseProxy;
                updatedBody["model"] = model = editOptions.Model;
                updatedBody["max_tokens"] = editOptions.MaxToken;
            }

            //if (updatedBody.TryGetPropertyValue("logprobs", out var logprobs) && logprobs?.GetValueKind() is JsonValueKind.Number)
            //{
            //    updatedBody["logprobs"] = true;
            //    updatedBody["top_logprobs"] = logprobs.GetValue<int>();
            //}
            updatedBody.Remove("logprobs");
            updatedBody.Remove("top_logprobs");

            if (isEditCompletions is false && (
                updatedBody.TryGetPropertyValue("max_tokens", out var maxToken) == false ||
                maxToken is null || maxToken.GetValue<int>() > baseOptions.MaxToken))
                updatedBody["max_tokens"] = baseOptions.MaxToken;

            if (updatedBody.TryGetPropertyValue("n", out var nNode) && nNode!.GetValue<int>() > 1)
                updatedBody["n"] = 1;

            bool isCodeWithChatUrl = apiUrl!.Contains("/chat/completions", StringComparison.OrdinalIgnoreCase);
            if (isCodeWithChatUrl)
            {
                if (isEditCompletions)
                {
                    var system = string.Empty;
                    var prompt = updatedBody["prompt"]!.ToString();
                    var match = SystemRegex.Match(prompt);
                    updatedBody["messages"] = new JsonArray() {
                        new JsonObject() { { "role", "system" }, { "content", match.Groups["system"].Value.Trim() } },
                        new JsonObject() { { "role", "user" }, { "content", match.Groups["prompt"].Value.Trim() } },
                    };
                    updatedBody.Remove("prompt");
                    updatedBody.Remove("speculation");
                }
                else
                {
                    const string system = """
                        你是一个精准的代码补全助手。
                        根据用户提供的前缀(prefix)和后缀(suffix)内容，生成中间缺失的合理代码。
                        其中前缀部分可能包含大量以//开头的上下文代码，必须要参照上下文合理补全。
                        请严格遵循以下规则：
                        1. 生成最小必要代码片段衔接上下文，多数情况下只补全一行有效代码。
                        2. 当无法确认后续逻辑时，直接结束输出，禁止做出任何假设性猜测内容。
                        3. 保持与前后代码完全一致的缩进和风格，禁止重复前缀已存在的内容（包括变量名）。
                        """;
                    var userMsg = $$"""
                        请只生成代码中间缺失部分，当前语言：{{updatedBody["extra"]?["language"]}}。
                        <prompt-start>
                        {{updatedBody["prompt"]}}
                        <prompt-end>
                        <suffix-start>
                        {{updatedBody["suffix"]}}
                        <suffix-end>
                        """;
                    updatedBody["messages"] = new JsonArray() {
                        new JsonObject() { { "role", "system" }, { "content", system } },
                        new JsonObject() { { "role", "user" }, { "content", userMsg } },
                    };
                    updatedBody.Remove("prompt");
                    updatedBody.Remove("suffix");
                }
            }

            updatedBody.Remove("extra");
            updatedBody.Remove("nwo");

            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(isEditCompletions ? 300 : 10));
            cts.Token.Register(() => httpContext.Abort()); // 超过 10s 不开始返回的中断请求，这种补全基本没用
            using var httpClient = httpClientFactory.CreateClient(useProxy ? "Proxy" : string.Empty);
            httpClient.Timeout = TimeSpan.FromSeconds(isEditCompletions ? 300 : 10);
            using var httpRequest = new HttpRequestMessage(HttpMethod.Post, apiUrl);
            var jsonString = updatedBody.ToJsonString(AppJsonSerializerContext.Web.Options);
            if (hostEnvironment.IsDevelopment()) Log.Information("Code Request: \r\n{jsonString}", jsonString);
            httpRequest.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
            httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
            httpContext.RequestAborted.ThrowIfCancellationRequested();
            isRequestSended = true; // 标识已发送请求到上游服务
            if (isCodeWithChatUrl) await ForwardStreamMsg2TxtAsync(httpClient, httpRequest, httpContext);
            else await ForwardStreamAsync(httpClient, httpRequest, httpContext);
            Log.Information("{ConnectionId}：补全接口调用完成。Model：{Model}，ApiKey:{ApiKey}。", httpContext.Connection.Id, model, $"{apiKey![..5]}***{apiKey![^5..]}");
        }
        catch (OperationCanceledException)
        {
            if (isRequestSended)
                Log.Information("{ConnectionId}：请求已成功发送至上游服务，但已主动中止接收后续响应数据。Model：{Model}，ApiKey:{ApiKey}。",
                    httpContext.Connection.Id, model, $"{apiKey![..5]}***{apiKey![^5..]}");
            try { httpContext.Response.StatusCode = StatusCodes.Status499ClientClosedRequest; } catch { }
            return;
        }
        catch (Exception ex)
        {
            Log.Error(ex, "{ConnectionId}：补全接口发生异常。Model：{Model}，ApiKey:{ApiKey}。",
                httpContext.Connection.Id, model, $"{apiKey![..5]}***{apiKey![^5..]}");
        }
    }

    private static readonly Regex ModelRegex = new Regex(@"You use the (.*?) model\.", RegexOptions.Compiled);
    public static async Task ChatCompletions(HttpContext httpContext,
        [FromServices] IHostEnvironment hostEnvironment,
        [FromServices] IHttpClientFactory httpClientFactory,
        [FromServices] IOptionsSnapshot<SystemConfig> systemConfig,
        [FromServices] IReadOnlyDictionary<string, SystemConfig.ChatCompletionsConfig> chatModels)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }

        JsonElement bodyJson;
        try { bodyJson = await httpContext.Request.ReadFromJsonAsync<JsonElement>(httpContext.RequestAborted); } catch { return; }
        var requestBody = JsonObject.Create(bodyJson)!;
        var sourceModelName = requestBody["model"]!.GetValue<string>();
        var modelConfig = chatModels.GetValueOrDefault(sourceModelName)?.GetNext();
        if (modelConfig is null)
        {
            httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
            return;
        }
        requestBody["model"] = modelConfig.Model;

        #region Copilot 的推荐提示词
        const string VS2022SoqTip = "Write a short one-sentence question that I can ask that naturally follows from the previous few questions and answers.";
        const string VSCodeSasTip = "Suggest a succinct (under 6 words) follow-up message to the last AI-assistant response.";
        const string VSCodeWbtTip = "Please write a brief title for the chat conversation above.";
        var requestMessages = requestBody["messages"]!.AsArray();
        var lastIsAiHelperMessage = requestMessages.TakeLast(1).Any(item =>
        {
            var content = item!.AsObject()["content"]!.ToString();
            return content.StartsWith(VS2022SoqTip) || content.StartsWith(VSCodeSasTip) || content.StartsWith(VSCodeWbtTip);
        });
        #endregion

        // 处理消息内容，补充回答语言提示词（用户输入的问答、主题标题和关联问题生成）
        var isMiniAssistantModel = sourceModelName.StartsWith("gpt-4o-mini");
        if (isMiniAssistantModel == false && lastIsAiHelperMessage == false && string.IsNullOrWhiteSpace(systemConfig.Value.ChatLanguage) == false)
        {
            var lastMessage = requestBody["messages"]!.AsArray().Last()!.AsObject();
            switch (lastMessage["content"]!.GetValueKind())
            {
                case JsonValueKind.String:
                    {
                        var content = lastMessage["content"]!.AsValue().ToString();
                        if (!content.Contains("Respond in the following locale"))
                            lastMessage["content"] = content + $"\r\nRespond in the following locale: {systemConfig.Value.ChatLanguage}.";
                        break;
                    }
                case JsonValueKind.Array:
                    {
                        var lastContent = lastMessage["content"]!.AsArray().Where(c => c!["type"]!.GetValue<string>() == "text").LastOrDefault();
                        if (lastContent != null)
                        {
                            var contentText = lastContent["text"]!.GetValue<string>();
                            if (!contentText.Contains("Respond in the following locale"))
                                lastContent["text"] = contentText + $"\r\nRespond in the following locale: {systemConfig.Value.ChatLanguage}.";
                        }
                        break;
                    }
            }

        }

        #region 删除工具调用的字段信息
        if (modelConfig is not { Supports.Tools: true })
        {
            requestBody.Remove("intent");
            requestBody.Remove("intent_threshold");
            requestBody.Remove("intent_content");
            requestBody.Remove("tools");
            requestBody.Remove("tool_call");
            requestBody.Remove("functions");
            requestBody.Remove("function_call");
            requestBody.Remove("tool_choice");
            var messages = requestBody["messages"]!.AsArray();
            for (int i = 0; i < messages.Count; i++)
            {
                var message = messages[i]!.AsObject();
                if (!message.TryGetPropertyValue("tool_calls", out _))
                    message.Remove("tool_calls");
            }
        }
        #endregion
        #region 更改提示词，移除专用AI限制
        foreach (var (index, item) in requestBody["messages"]!.AsArray().Index())
        {
            if (index > 0) break;
            var message = item!.AsObject()!;
            var content = message["content"]!.ToString();
            const string copilotVsCode = "you must respond with \"GitHub Copilot\".";
            if (content.Contains(copilotVsCode)) content = content.Replace(copilotVsCode, $"you can respond with \"GitHub Copilot\" and use the {modelConfig.Name ?? modelConfig.Model} model.");
            const string policyVsCode = "Follow Microsoft content policies.\n";
            if (content.Contains(policyVsCode)) content = content.Replace(policyVsCode, string.Empty);
            const string avoidVsCode = "Avoid content that violates copyrights.\n";
            if (content.Contains(avoidVsCode)) content = content.Replace(avoidVsCode, string.Empty);
            var matchModel = ModelRegex.Match(content);
            if (matchModel.Success) content = content.Replace(matchModel.Value, $"You use the {modelConfig.Name ?? modelConfig.Model} model.");
            // 取消无关开发话题过滤提示
            const string relatedTip = "For questions not related to software development, simply give a reminder that you are an AI programming assistant.\r\n";
            if (content.Contains(relatedTip)) content = content.Replace(relatedTip, string.Empty);
            const string skipTip = """, or completely irrelevant to software engineering""";
            if (content.Contains(skipTip)) content = content.Replace(skipTip, string.Empty);
            message["content"] = content;
        }
        #endregion

        if (requestBody.TryGetPropertyValue("max_tokens", out var maxToken) == false || maxToken!.GetValue<int>() > modelConfig.MaxToken)
            requestBody["max_tokens"] = modelConfig.MaxToken;
        if (requestBody.TryGetPropertyValue("n", out var nNode) && nNode!.GetValue<int>() > 1)
            requestBody["n"] = 1;

        try
        {
            using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(300));
            cts.Token.Register(() => httpContext.Abort());
            using var httpClient = httpClientFactory.CreateClient(modelConfig.UseProxy ? "Proxy" : string.Empty);
            httpClient.Timeout = TimeSpan.FromSeconds(300);

            var jsonString = requestBody.ToJsonString(AppJsonSerializerContext.Web.Options);
            if (hostEnvironment.IsDevelopment()) Log.Information("Chat Request: \r\n{jsonString}", jsonString);
            using var httpRequest = new HttpRequestMessage(HttpMethod.Post, modelConfig.ApiUrl);
            httpRequest.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
            httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", modelConfig.ApiKey);
            try { await ForwardStreamReasoning2ContentAsync(httpClient, httpRequest, httpContext); }
            catch (HttpRequestException) when (httpContext.Response.StatusCode == StatusCodes.Status429TooManyRequests)
            {
                Log.Warning("{ConnectionId}：已捕获繁忙请求，将在 1~3 秒后重试！", httpContext.Connection.Id);
                await Task.Delay(Random.Shared.Next(1000, 3000)); // 稍后重试
                Log.Warning("{ConnectionId}：已开始重发请求，再次失败将直接结束。", httpContext.Connection.Id);
                using var retryRequest = new HttpRequestMessage(HttpMethod.Post, modelConfig.ApiUrl);
                retryRequest.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
                retryRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", modelConfig.ApiKey);
                await ForwardStreamReasoning2ContentAsync(httpClient, retryRequest, httpContext);
                Log.Warning("{ConnectionId}：已成功重发请求并得到有效的响应内容。", httpContext.Connection.Id);
            }
            Log.Information("{ConnectionId}：聊天接口调用完成。Model：{Model}，ApiKey:{ApiKey}。", httpContext.Connection.Id, modelConfig.Model, $"{modelConfig.ApiKey![..5]}***{modelConfig.ApiKey![^5..]}");
        }
        catch (OperationCanceledException) { return; }
        catch (Exception ex)
        {
            Log.Error(ex, "{ConnectionId}：聊天接口发生异常。Model：{Model}，ApiKey:{ApiKey}。",
                httpContext.Connection.Id, modelConfig.Model, $"{modelConfig.ApiKey![..5]}***{modelConfig.ApiKey![^5..]}");
        }
    }

    #region Embeddings
    const int ChunkLength = 5;
    const int DefaultChunkSize = 768;
    const int MaxChunkSize = 1024 * 3 / 2;
    static readonly SemaphoreSlim Semaphore = new SemaphoreSlim(10);
    #region Chunks Models
    public class ChunkRequest
    {
        [JsonPropertyName("content")]
        public required string Content { get; set; }
        [JsonPropertyName("path")]
        public required string Path { get; set; }
        [JsonPropertyName("embed")]
        public bool Embed { get; set; }
    }
    public class Chunk
    {
        [JsonPropertyName("hash")]
        public required string Hash { get; set; }
        [JsonPropertyName("text")]
        public required string Text { get; set; }
        [JsonPropertyName("range")]
        public required RangeNode Range { get; set; }
        [JsonPropertyName("embedding")]
        public required ChunkEmbeddingNode Embedding { get; set; }
        public class RangeNode
        {
            [JsonPropertyName("start")]
            public int Start { get; set; }
            [JsonPropertyName("end")]
            public int End { get; set; }
        }
        public class ChunkEmbeddingNode
        {
            [JsonPropertyName("embedding")]
            public required float[] Embedding { get; set; }
        }
    }
    public class ChunkResponse
    {
        [JsonPropertyName("chunks")]
        public required List<Chunk> Chunks { get; set; }
        [JsonPropertyName("embedding_model")]
        public required string EmbeddingModel { get; set; }
    }
    public class EmbeddingDataResponse
    {
        [JsonPropertyName("data")]
        public DataNode[] Data { get; set; } = Array.Empty<DataNode>();
        public class DataNode
        {
            [JsonPropertyName("index")]
            public int Index { get; set; }
            [JsonPropertyName("embedding")]
            public float[] Embedding { get; set; } = Array.Empty<float>();
        }
    }
    #endregion
    #region Embedding Methods
    static List<Chunk> ChunkRequestContext(ChunkRequest request)
    {
        var chunks = new List<Chunk>();
        var lines = request!.Content.Split('\n');
        var currentChunk = new StringBuilder(DefaultChunkSize);
        int start = 0;
        static Chunk CreateChunk(string text, string path, int start, int end)
        {
            using var sha256 = SHA256.Create();
            var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(text));
            var hash = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
            return new Chunk
            {
                Hash = hash,
                Text = $"File: `{path}`\n```shell\n{text}```",
                Range = new() { Start = start, End = end },
                Embedding = new() { Embedding = Array.Empty<float>() }
            };
        }
        using (var reader = new StringReader(request!.Content))
        {
            string? line;
            while ((line = reader.ReadLine()) != null)
            {
                void AppendPreHandledChunk()
                {
                    if (currentChunk.Length > 0)
                    {
                        var chunk = CreateChunk(currentChunk.ToString(), request.Path, start, start + currentChunk.Length);
                        chunks.Add(chunk);
                        start += currentChunk.Length;
                        currentChunk.Clear();
                    }
                }

                if (line.Length > DefaultChunkSize)
                {
                    AppendPreHandledChunk();
                    int lineStart = 0;
                    while (lineStart < line.Length)
                    {
                        int length = Math.Min(MaxChunkSize, line.Length - lineStart);
                        var chunkText = line.Substring(lineStart, length);
                        var chunk = CreateChunk(chunkText, request.Path, start, start + chunkText.Length);
                        chunks.Add(chunk);
                        start += chunkText.Length;
                        lineStart += length;
                    }
                }
                else
                {
                    if (currentChunk.Length + line.Length + 1 > DefaultChunkSize)
                        AppendPreHandledChunk();
                    currentChunk.AppendLine(line);
                }
            }
        }
        if (currentChunk.Length > 0)
        {
            var chunk = CreateChunk(currentChunk.ToString(), request.Path, start, start + currentChunk.Length);
            chunks.Add(chunk);
        }
        return chunks;
    }
    static async Task<EmbeddingDataResponse> GetEmbeddingDataAsync(HttpClient httpClient, HttpRequestMessage httpRequest, HttpContext httpContext)
    {
        using var response = await httpClient.SendAsync(httpRequest, httpContext.RequestAborted);
        try { response.EnsureSuccessStatusCode(); }
        catch
        {
            try { Log.Warning(await response.Content.ReadAsStringAsync()); }
            finally { httpContext.Response.StatusCode = (int)response.StatusCode; }
            throw;
        }
        using var responseStream = await response.Content.ReadAsStreamAsync(httpContext.RequestAborted);
        var responseObject = await httpContext.DeserializeStreamAsync<EmbeddingDataResponse>(responseStream);
        if (responseObject is null or { Data: null }) throw new HttpRequestException("Embedding data response is null");
        return responseObject;
    }
    #region DimensionalityReduction
    static float[] LinearInterpolation(float[] input, int targetLength)
    {
        if (input is null or { Length: 0 }) return input!;
        float[] output = new float[targetLength];
        float scale = (float)(input.Length - 1) / (targetLength - 1);
        for (int i = 0; i < targetLength; i++)
        {
            float position = i * scale;
            int index = (int)position;
            float fraction = position - index;
            if (index >= input.Length - 1)
                output[i] = input[input.Length - 1];
            else // 线性插值公式：y = y0 + (y1 - y0) * fraction
                output[i] = input[index] + (input[index + 1] - input[index]) * fraction;
        }
        return output;
    }
    static EmbeddingDataResponse DimensionalityReduction(EmbeddingDataResponse dataResponse)
    {
        if (dataResponse.Data.All(ea => ea is { Embedding.Length: 512 })) return dataResponse;
        dataResponse.Data = dataResponse.Data.Select(ea => { ea.Embedding = LinearInterpolation(ea.Embedding, 512); return ea; }).ToArray();
        return dataResponse;
    }
    static ChunkResponse DimensionalityReduction(ChunkResponse dataResponse)
    {
        if (dataResponse.Chunks.All(ea => ea.Embedding is { Embedding.Length: 512 })) return dataResponse;
        dataResponse.Chunks = dataResponse.Chunks.Select(ea => { ea.Embedding.Embedding = LinearInterpolation(ea.Embedding.Embedding, 512); return ea; }).ToList();
        return dataResponse;
    }
    #endregion
    #endregion
    public static async Task HandleChunks(HttpContext httpContext,
        [FromServices] IHostEnvironment hostEnvironment,
        [FromServices] IHttpClientFactory httpClientFactory,
        [FromKeyedServices("Chunks")] SystemConfig.BaseModelConfig options)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }
        if (string.IsNullOrEmpty(options.ApiKey))
        {
            httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
            return;
        }

        if (!await Semaphore.WaitAsync(TimeSpan.FromSeconds(10), httpContext.RequestAborted))
        {
            httpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
            return;
        }

        try
        {
            ChunkRequest? request = null;
            try { request = await httpContext.Request.ReadFromJsonAsync<ChunkRequest>(httpContext.RequestAborted)!; } catch { return; }
            var chunks = ChunkRequestContext(request!);
            var dataResponse = new ChunkResponse { Chunks = chunks, EmbeddingModel = options.Model!, };
            if (request!.Embed)
            {
                using var httpClient = httpClientFactory.CreateClient(options.UseProxy ? "Proxy" : string.Empty);
                await Parallel.ForEachAsync(chunks.Chunk(ChunkLength), httpContext.RequestAborted, async (batchChunk, token) =>
                {
                    if (token.IsCancellationRequested) return;

                    var texts = batchChunk.Select(chunk =>
                    {
                        var textSpan = chunk.Text.AsSpan();
                        var ignoreFirstLine = textSpan.IndexOf('\n');
                        if (ignoreFirstLine != -1) textSpan = textSpan[(ignoreFirstLine + 1)..];
                        if (textSpan.StartsWith("```shell\n")) textSpan = textSpan[9..];
                        if (textSpan.EndsWith("```")) textSpan = textSpan[..^3];
                        return textSpan.ToString();
                    }).ToArray();

                    var reqJson = $$"""
                    {
                        "model":"{{options.Model}}",
                        "input":{{texts.SerializeToJsonString()}},
                        "encoding_format": "float",
                        "dimensions": 512
                    }
                    """;
                    using var httpRequest = new HttpRequestMessage(HttpMethod.Post, options.ApiUrl);
                    if (hostEnvironment.IsDevelopment()) Log.Information("Chunks Embed Request: \r\n{reqJson}", reqJson);
                    httpRequest.Content = new StringContent(reqJson, Encoding.UTF8, "application/json");
                    httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey);
                    var responseObject = await GetEmbeddingDataAsync(httpClient, httpRequest, httpContext);
                    var embeddings = responseObject.Data;
                    foreach (var (index, chunk) in batchChunk.Index())
                        chunk.Embedding.Embedding = embeddings[index].Embedding;
                });
                dataResponse = DimensionalityReduction(dataResponse);
            }
            await httpContext.Response.WriteAsJsonAsync(dataResponse, AppJsonSerializerContext.Default.Options);
            Log.Information("{ConnectionId}：Chunks 处理完毕。Model：{Model}，ApiKey:{ApiKey}。", httpContext.Connection.Id, options.Model, $"{options.ApiKey![..5]}***{options.ApiKey![^5..]}");
        }
        catch (OperationCanceledException) { return; }
        catch (Exception ex)
        {
            Log.Error(ex, "{ConnectionId}：处理 Chunks 时发生异常。Model：{Model}，ApiKey:{ApiKey}",
                httpContext.Connection.Id, options.Model, $"{options.ApiKey![..5]}***{options.ApiKey![^5..]}");
            if (httpContext.Response.StatusCode == StatusCodes.Status200OK)
                httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
        }
        finally { Semaphore.Release(); }
    }
    public static async Task HandleEmbeddings(HttpContext httpContext,
        [FromServices] IHostEnvironment hostEnvironment,
        [FromServices] IHttpClientFactory httpClientFactory,
        [FromKeyedServices("Embeddings")] SystemConfig.BaseModelConfig options)
    {
        if (httpContext.Request.Headers.Authorization is { Count: 0 })
        {
            httpContext.Response.StatusCode = StatusCodes.Status401Unauthorized;
            return;
        }
        if (string.IsNullOrEmpty(options.ApiKey))
        {
            httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
            return;
        }

        if (!await Semaphore.WaitAsync(TimeSpan.FromSeconds(10), httpContext.RequestAborted))
        {
            httpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
            return;
        }

        try
        {
            JsonNode? parseNode = null;
            try { parseNode = await JsonObject.ParseAsync(httpContext.Request.Body, null, default, httpContext.RequestAborted); }
            catch
            {
                httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
                return;
            }

            using var httpClient = httpClientFactory.CreateClient(options.UseProxy ? "Proxy" : string.Empty);

            var updatedBody = parseNode!.AsObject();
            updatedBody["model"] = options.Model;
            var input = updatedBody["input"]?.AsArray();
            if (input is { Count: > ChunkLength }) // 减少超过最大请求长度的可能性
            {
                var embedData = new ConcurrentQueue<EmbeddingDataResponse.DataNode[]>();
                await Parallel.ForEachAsync(input.GetValues<string>().Chunk(ChunkLength).Index(), httpContext.RequestAborted, async (chunkWithIndex, token) =>
                {
                    if (token.IsCancellationRequested) return;

                    var (index, chunk) = chunkWithIndex;
                    var reqJson = $$"""
                    {
                        "model":"{{options.Model}}",
                        "input":{{chunk.SerializeToJsonString()}},
                        "encoding_format": "float",
                        "dimensions": 512
                    }
                    """;
                    using var httpRequest = new HttpRequestMessage(HttpMethod.Post, options.ApiUrl);
                    if (hostEnvironment.IsDevelopment()) Log.Information("Embeddings Chunk Request: \r\n{jsonString}", reqJson);
                    httpRequest.Content = new StringContent(reqJson, Encoding.UTF8, "application/json");
                    httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey);
                    var responseObject = await GetEmbeddingDataAsync(httpClient, httpRequest, httpContext);
                    embedData.Enqueue(responseObject.Data.Select((item, idx) => { item.Index = index * ChunkLength + idx; return item; }).ToArray());
                });
                var dataResponse = new EmbeddingDataResponse() { Data = embedData.SelectMany(x => x).OrderBy(x => x.Index).ToArray() };
                await httpContext.ResponseJsonAsync(DimensionalityReduction(dataResponse));
            }
            else
            {
                using var httpRequest = new HttpRequestMessage(HttpMethod.Post, options.ApiUrl);
                var jsonString = updatedBody.ToJsonString(AppJsonSerializerContext.Web.Options);
                if (hostEnvironment.IsDevelopment()) Log.Information("Embeddings Request: \r\n{jsonString}", jsonString);
                httpRequest.Content = new StringContent(jsonString, Encoding.UTF8, "application/json");
                httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", options.ApiKey);
                var responseObject = await GetEmbeddingDataAsync(httpClient, httpRequest, httpContext);
                await httpContext.ResponseJsonAsync(DimensionalityReduction(responseObject));
            }
            Log.Information("{ConnectionId}：Embeddings 处理完毕。Model：{Model}，ApiKey:{ApiKey}。", httpContext.Connection.Id, options.Model, $"{options.ApiKey![..5]}***{options.ApiKey![^5..]}");
        }
        catch (OperationCanceledException) { return; }
        catch (Exception ex)
        {
            Log.Error(ex, "{ConnectionId}：处理 Embeddings 时发生异常。Model：{Model}，ApiKey:{ApiKey}。",
                httpContext.Connection.Id, options.Model, $"{options.ApiKey![..5]}***{options.ApiKey![^5..]}");
            if (httpContext.Response.StatusCode == StatusCodes.Status200OK)
                httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
        }
        finally { Semaphore.Release(); }
    }
    #endregion
}
